/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstring>
#include <stdlib.h>

#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);

void getBounds(const vector<SelCond>& cond, int attr, int* &eq, int* &max, int* &min, int* &ne);
bool isValidValue(const vector<SelCond>& cond, const char* str);

RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

bool isValidValue(const vector<SelCond>& cond, const char* str){

	int diff;

	for (unsigned i = 0; i < cond.size(); i++) {
		if(cond[i].attr != 2)
			continue;

		diff = strcmp(str, cond[i].value);


		// skip the tuple if any condition is not met
		switch (cond[i].comp) {
		case SelCond::EQ:
			if (diff != 0)
				return false;
			break;

		case SelCond::NE:
			if (diff == 0)
				return false;
			break;

		case SelCond::GT:
			if (diff <= 0)
				return false;
			break;

		case SelCond::LT:
			if (diff >= 0)
				return false;
			break;

		case SelCond::GE:
			if (diff < 0)
				return false;
			break;

		case SelCond::LE:
			if (diff > 0)
				return false;
			break;
		}
	}

	return true;

}

void getBounds(const vector<SelCond>& cond, int attr, int* &eq, int* &max, int* &min, int* &ne){

	eq  = NULL;
	ne  = NULL;

	max = NULL;
	min = NULL;

	int temp;

	for (unsigned i = 0; i < cond.size(); i++) {

		//Filter the attributes that we are not looking for.
		if(cond[i].attr != attr)
			continue;

		int value = atoi(cond[i].value);

		switch (cond[i].comp) {

		//key == val
		case SelCond::EQ:
			eq = (int*)malloc(sizeof(int));
			*eq = value;
			break;

		//key != val
		case SelCond::NE:
			ne = (int*)malloc(sizeof(int));
			*ne = value;
			break;

		//key >= val
		case SelCond::GE:

			if(min == NULL || *min < value){
				min = (int*)malloc(sizeof(int));
				*min = value;
			}

			break;

		//key > val
		case SelCond::GT:
			if(min == NULL || *min < value + 1){
				min = (int*)malloc(sizeof(int));
				*min = ++value;
			}
			break;

		//key <= val
		case SelCond::LE:
			if(max == NULL || *max > value){
				max = (int*)malloc(sizeof(int));
				*max = value;
			}
			break;

		//key <= val
		case SelCond::LT:
			if(max == NULL || *max > value - 1){
				max = (int*)malloc(sizeof(int));
				*max = --value;
			}
			break;
		}


	}

}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	BTreeIndex index;

	RecordFile rf;   // RecordFile containing the table
	RecordId   rid;  // record cursor for table scanning

	RC     rc;
	int    key;
	string value;
	int    count;
	int    diff;

	//Open the table file.
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}

	int* keyEq  = NULL;
	int* keyNe  = NULL;
	int* keyMax = NULL;
	int* keyMin = NULL;

	getBounds(cond, 1, keyEq, keyMax, keyMin, keyNe);
	bool keyFilterSet = keyEq != NULL || keyNe != NULL || keyMax != NULL || keyMin != NULL;

	//Don't even bother trying to open up the index file, if there are no filters set
	//on the key.
	if (keyFilterSet && (index.open(table + ".idx", 'r')) == 0) {

		bool valFilterSet = false;
		for (unsigned i = 0; i < cond.size(); i++) {
			if(cond[i].attr == 2)
				valFilterSet = true;
		}

		/*
		printf("KEQ %d \t KNE %d\t KMIN %d \t KMAX %d \n", (keyEq != NULL)? *keyEq : -1,
													   (keyNe != NULL)? *keyNe : -1,
													   (keyMin != NULL)? *keyMin : -1,
													   (keyMax != NULL)? *keyMax : -1);

		*/

		int searchKey;

		//Set the lower bound of the search. This allows us
		//to assume that we are going to go left to right in
		//our search.
		if(keyEq != NULL){
			searchKey = *keyEq;

		}else if(keyMin != NULL){
			searchKey = *keyMin;

		}else{
			searchKey = -1;
		}

		int key;
		IndexCursor cursor;
		RecordId rid;

		//Locate the search key.
		if ((rc = index.locate(searchKey, cursor)) != 0) {
			index.close();
			return rc;
		}

		while(index.readForward(cursor, key, rid) == 0) {

			if(keyEq != NULL && key != *keyEq){
				break;
			}

			if(keyMin != NULL && key < *keyMin){
				break;
			}

			if(keyMax != NULL && key > *keyMax){
				break;
			}

			if(keyNe !=  NULL && key == *keyNe){
				continue;
			}


			//There is no reason to read in a page, if we are just interested in the key.
			if ((valFilterSet || attr == 2 || attr == 3) && (rc = rf.read(rid, key, value)) != 0) {
				fprintf(stderr, "Error: could not read from table %s\n", table.c_str());
				index.close();
				return rc;
			}

			if(valFilterSet && !isValidValue(cond, value.c_str())){
				continue;
			}

			count++;


			switch (attr) {

			//Output Key:
			case 1:
			  fprintf(stdout, "%d\n", key);
			  break;

			//Output Value:
			case 2:
			  fprintf(stdout, "%s\n", value.c_str());
			  break;

			//Output key and value:
			case 3:
			  fprintf(stdout, "%d '%s'\n", key, value.c_str());
			  break;
			}

		}//End of main while.

	if(attr == 4) {
		fprintf(stdout, "%d\n", count);
	}


  //This is the original code that reads the entire disk to
  //find the key matches. Degrade down to this approach when
  //there is no index file.
  }else{

	  // scan the table file from the beginning
	  rid.pid = rid.sid = 0;
	  count = 0;
	  while (rid < rf.endRid()) {
		// read the tuple
		if ((rc = rf.read(rid, key, value)) < 0) {
		  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  goto exit_select;
		}

		// check the conditions on the tuple
		for (unsigned i = 0; i < cond.size(); i++) {
		  // compute the difference between the tuple value and the condition value
		  switch (cond[i].attr) {
			  case 1:
				  diff = key - atoi(cond[i].value);
				  break;
			  case 2:
				  diff = strcmp(value.c_str(), cond[i].value);
				  break;
		  }

		  // skip the tuple if any condition is not met
		  switch (cond[i].comp) {
		  case SelCond::EQ:
		if (diff != 0) goto next_tuple;
		break;
		  case SelCond::NE:
		if (diff == 0) goto next_tuple;
		break;
		  case SelCond::GT:
		if (diff <= 0) goto next_tuple;
		break;
		  case SelCond::LT:
		if (diff >= 0) goto next_tuple;
		break;
		  case SelCond::GE:
		if (diff < 0) goto next_tuple;
		break;
		  case SelCond::LE:
		if (diff > 0) goto next_tuple;
		break;
		  }
		}

		// the condition is met for the tuple.
		// increase matching tuple counter
		count++;

		// print the tuple
		switch (attr) {
		case 1:  // SELECT key
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2:  // SELECT value
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:  // SELECT *
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
		}

		// move to the next tuple
		next_tuple:
		++rid;
	  }

	  // print matching tuple count if "select count(*)"
	  if (attr == 4) {
		fprintf(stdout, "%d\n", count);
	  }
  }


  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool useIndex)
{
	RecordFile* record = new RecordFile(table + ".tbl", 'w');

	BTreeIndex index;

	if(useIndex) {
		index.open(table + ".idx", 'w');
	}

	ifstream ifs;

	ifs.open(loadfile.c_str());

	string line;
	int key;

	while (ifs.good() && getline(ifs, line, '\n')) {
		string value;
		RecordId id;

		if (parseLoadLine(line, key, value) == 0 && record->append(key, value, id) == 0) {
				if(useIndex && index.insert(key, id) != 0) {
					cout << "File write failed, exiting." << endl;
					return RC_FILE_WRITE_FAILED;
				}
		} else {

			return RC_INVALID_ATTRIBUTE;
		}
	}

	index.close();
	record->close();
	delete(record);

	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
